/*
 * Created by Icodeon Ltd.
 * Date: 17/08/2008
 * 
 * Copyright 2008 All Rights Reserved
 * Icodeon Ltd 
 * St Johns Innovation Centre
 * Cowley Road
 * Cambridge 
 * CB4 0WS United Kingdom
 * 
 * Registered in England and Wales No: 5068195
 * Use is subject to license terms.
 */

using System;
using System.IO;
using System.Xml;
using System.Collections;
using System.Diagnostics;

using Icodeon.Scorm2CC.Exceptions;
using Icodeon.Xml;
using Icodeon.Xhtml;
using Icodeon.Util;

namespace Icodeon.Scorm2CC
{
    public class PackageConverterImpl : IPackageConverter
    {
        private XsltTransformer xsltTransformer;
        private IOUtils ioUtils;
        private ScormUtils scormUtils;
        private XmlUtils xmlUtils;
        private int packageProgess;
        private string packageStatus;


        #region PackageConverterImpl constructor

        public PackageConverterImpl()
        {
            this.xsltTransformer = new XsltTransformer();
            this.ioUtils = new IOUtils();
            this.scormUtils = new ScormUtils();
            this.xmlUtils = new XmlUtils();
            this.packageProgess = 0;
        }

        #endregion

        #region public Interface implementation methods

        public bool ContainsImsManifest(DirectoryInfo scormPackageDirectory,
            IObserver observer)
        {
            if (observer != null)
            {
                this.packageProgess = 10;
                string status = String.Format("Package {0} check manifest ...", scormPackageDirectory.Name);
                observer.Callback(this.packageProgess, status);
            }

            FileInfo imsManifest = ioUtils.CombineDirectoryNameAndFileName(scormPackageDirectory, "imsmanifest.xml");
            return imsManifest.Exists;
        }

        public void TransformImsManifest(
            DirectoryInfo scormPackageDirectory,
            DirectoryInfo commonCartridgeDirectory,
            FileInfo xslFileForScorm1p2,
            FileInfo xslFileForScorm2004,
            IObserver observer)
        {
            if (!xslFileForScorm1p2.Exists)
            {
                throw new Scorm2CCException("Cannot find transform file: " + xslFileForScorm1p2.FullName);
            }
            if (!xslFileForScorm2004.Exists)
            {
                throw new Scorm2CCException("Cannot find transform file: " + xslFileForScorm2004.FullName);
            }

            if (observer != null)
            {
                this.packageProgess = 90;
                string status = String.Format("Package {0} transform ...",
                        scormPackageDirectory.Name);
                observer.Callback(this.packageProgess, status);
            }

            // Input 
            FileInfo imsManifestScorm = ioUtils.CombineDirectoryNameAndFileName(scormPackageDirectory, "imsmanifest.xml");

            // Output
            FileInfo imsManifestCommonCartridge = ioUtils.CombineDirectoryNameAndFileName(commonCartridgeDirectory, "imsmanifest.xml");

            XmlDocument doc = new XmlDocument();
            doc.Load(imsManifestScorm.FullName);
            XmlElement manifest = doc.DocumentElement;
            ScormVersion scormVersion = this.scormUtils.GetScormVersion(manifest);

            if (scormVersion == ScormVersion.scorm1p2)
            {
                this.xsltTransformer.Transform(
                    imsManifestScorm,
                    xslFileForScorm1p2,
                    imsManifestCommonCartridge);
            }
            else
            {
                this.xsltTransformer.Transform(
                    imsManifestScorm,
                    xslFileForScorm2004,
                    imsManifestCommonCartridge);
            }

            if (observer != null)
            {
                this.packageProgess = 100;
                string status = String.Format("Package {0} converted.",
                        scormPackageDirectory.Name, scormVersion);
                observer.Callback(this.packageProgess, status);
            }
        }

        public void WrapAndCopyResourceFiles(
            DirectoryInfo scormPackageDirectory,
            DirectoryInfo commonCartridgeDirectory,
            DirectoryInfo scorm2ccResourceDirectory,
            IObserver observer)
        {
            if (!commonCartridgeDirectory.Exists)
            {
                Directory.CreateDirectory(commonCartridgeDirectory.FullName);
            }
            FileInfo imsManifestScorm = ioUtils.CombineDirectoryNameAndFileName(scormPackageDirectory, "imsmanifest.xml");
            FileInfo imsManifestCommonCartridge = ioUtils.CombineDirectoryNameAndFileName(commonCartridgeDirectory, "imsmanifest.xml");
            XmlDocument doc = new XmlDocument();
            doc.Load(imsManifestScorm.FullName);

            XmlElement manifest = doc.DocumentElement;

            // Get the resources element
            XmlElement resources = this.GetResources(manifest);

            // Build an collection of resource elements
            XmlElement[] resourceElements = this.GetResourceElements(resources);

            // Get the SCORM version
            ScormVersion scormVersion = this.scormUtils.GetScormVersion(manifest);

            // Get a reference to one of the pre-built wrapper framesets
            FileInfo scorm2ccWrapperResource =
                ioUtils.CombineDirectoryNameAndFileName(scorm2ccResourceDirectory, "wrapper_2004.html");
            if (scormVersion == ScormVersion.scorm1p2)
            {
                scorm2ccWrapperResource = ioUtils.CombineDirectoryNameAndFileName(scorm2ccResourceDirectory, "wrapper_1p2.html");
            }

            // Read in a pre-built wrapper frameset
            XhtmlDocumentStrict xhtmlStrict = new XhtmlDocumentStrict();
            xhtmlStrict.Load(scorm2ccWrapperResource);
            if (observer != null)
            {
                string status = String.Format("Reading {0} ...", scorm2ccWrapperResource.Name);
                observer.Callback(this.packageProgess, status);
            }

            // should be caching and cloning this rather than reading in...
            IHtml html = xhtmlStrict.RootElement;
            IFrameset topLevelFrameSet = html.GetTopLevelFrameset();
            IFrame[] frames = topLevelFrameSet.GetFrames();
            IFrame frame = frames[0];

            /*
             * Iterate over the list:
             * 
             * (1) Copy the file referenced by the href attribute, stripping off any parameters
             * 
             * (2) IF the resource has scormType/scormtype of "SCO" THEN
             *      Load the 2004 or 1.2 wrapper
             *      Add the value of the href attribute to the frame scource attribute of the wrapper
             *      Serialize the wrapper to the CC package with a (short) GUID as XHTML file
             *      Change the href attribute value to the (short) GUID as XHTML file
             *      Add the icodeon.scorm.2cc.*.js file(s)
             * 
             * (3) Copy the files referenced by the file elements, stripping off any parameters
             *      IF the resource has scormType/scormtype of "SCO" THEN
             *      Add a new file element with the wrapper frameset name
             * 
             * (4) Add the original file name of the SCO and the icodeon.scorm.2cc.*.js file(s) to the 
             * resource element.
             * 
             */

            FileInfo scormResource = null;
            FileInfo commonCartridgeResource = null;
            ScormType scormType = ScormType.asset;
            string wrapperFileName = null;

            for (int i = 0; i < resourceElements.Length; i++)
            {

                //(1) The resource element
                string resourceHref = this.xmlUtils.GetAttributeValue(resourceElements[i], "href");
                if (!String.IsNullOrEmpty(resourceHref))
                {
                    // a resource element with href attribute

                    string resourceHrefNoParam = this.ioUtils.RemoveUrlParameterString(resourceHref);
                    // The full path to the SCORM resource 
                    scormResource = ioUtils.CombineDirectoryNameAndFileName(scormPackageDirectory, resourceHrefNoParam);
                    // Create the full path to copy the SCORM resource to CC directory
                    commonCartridgeResource = ioUtils.CombineDirectoryNameAndFileName(commonCartridgeDirectory, resourceHrefNoParam);
                    // Make the directories in the Common Cartridge Package
                    ioUtils.MakeDirectoriesInPath(commonCartridgeResource);

                    // Copy the SCORM resource to CC directory
                    if (observer != null)
                    {
                        int progress = Convert.ToInt32(80 * (Convert.ToDecimal(i) / Convert.ToDecimal(resourceElements.Length)));
                        string status = String.Format("Copy {0} ...", scormResource.Name);
                        observer.Callback(this.packageProgess + progress, status);
                    }
                    File.Copy(scormResource.FullName, commonCartridgeResource.FullName, true);

                    // (2)
                    scormType = this.scormUtils.GetScormType(resourceElements[i], scormVersion);
                    wrapperFileName = null;
                    if (scormType == ScormType.sco)
                    {
                        // Set the frame referenece to the original SCORM resource element
                        frame.SetSrc(resourceHref);

                        // Generate a unique file name for the frameset
                        wrapperFileName = "sco_wrapper_" + ioUtils.GetFileNameWithoutExtension(scormResource) + ".html";

                        // Create the full path to copy the SCORM resource to CC directory
                        commonCartridgeResource = ioUtils.CombineDirectoryNameAndFileName(commonCartridgeDirectory, wrapperFileName);

                        // Serialize the the frameset to the CC directory
                        xhtmlStrict.Serialize(commonCartridgeResource);
                        if (observer != null)
                        {
                            string status = String.Format("Serialize {0} ...", wrapperFileName);
                            observer.Callback(this.packageProgess, status);
                        }

                        // Update the resource element href with the wrapper frameset
                        this.xmlUtils.SetAttributeValue(resourceElements[i], "href", wrapperFileName);

                        // Copy the scorm2cc *.js files
                        String[] jsFiles = { "icodeon.xml.js", "icodeon.rte.js", "icodeon.cocd.js" };
                        this.CopyScorm2CCFiles(jsFiles, scorm2ccResourceDirectory, commonCartridgeDirectory, observer);
                    }
                }

                // (3) The file elements
                XmlElement[] fileElements = this.GetFileElements(resourceElements[i]);
                for (int j = 0; j < fileElements.Length; j++)
                {
                    string fileHref = this.xmlUtils.GetAttributeValue(fileElements[j], "href");

                    fileHref = this.ioUtils.RemoveUrlParameterString(fileHref);
                    scormResource = ioUtils.CombineDirectoryNameAndFileName(scormPackageDirectory, fileHref);
                    commonCartridgeResource = ioUtils.CombineDirectoryNameAndFileName(commonCartridgeDirectory, fileHref);
                    // Make the directories in the Common Cartridge Package
                    ioUtils.MakeDirectoriesInPath(commonCartridgeResource);
                    // Copy the resource files
                    if (observer != null)
                    {
                        string status = String.Format("Copy {0} ...", scormResource.Name);
                        observer.Callback(this.packageProgess, status);
                    }
                    File.Copy(scormResource.FullName, commonCartridgeResource.FullName, true);
                }

                // (4) Add additional file elements if this is a scormType = sco
                if (scormType == ScormType.sco)
                {
                    this.AddNewFileElementToResource(resourceElements[i], wrapperFileName, manifest.NamespaceURI);
                    this.AddNewFileElementToResource(resourceElements[i], "icodeon.xml.js", manifest.NamespaceURI);
                    this.AddNewFileElementToResource(resourceElements[i], "icodeon.cocd.js", manifest.NamespaceURI);
                    this.AddNewFileElementToResource(resourceElements[i], "icodeon.rte.js", manifest.NamespaceURI);
                }
            }

            // All work done, now serialize the SCORM manifest to the common cartridge directory, ready for a transform
            Icodeon.Xml.Serializer serializer = new Icodeon.Xml.Serializer();
            serializer.SerializeToFile(manifest, imsManifestScorm, manifest.NamespaceURI);
        }

        #endregion

        #region ConverterImpl private supporting methods

        private void CopyScorm2CCFiles(
            String[] filenames,
            DirectoryInfo scorm2ccResourceDirectory,
            DirectoryInfo commonCartridgeDirectory,
            IObserver observer)
        {
            for (int i = 0; i < filenames.Length; i++)
            {
                String javaScriptFileileName = filenames[i];
                FileInfo scorm2ccScriptResource = ioUtils.CombineDirectoryNameAndFileName(scorm2ccResourceDirectory, javaScriptFileileName);
                FileInfo commonCartridgeScriptResource = ioUtils.CombineDirectoryNameAndFileName(commonCartridgeDirectory, javaScriptFileileName);

                if (observer != null)
                {
                    string status = String.Format("Copy {0} ...", scorm2ccScriptResource.Name);
                    observer.Callback(this.packageProgess, status);
                }
                File.Copy(scorm2ccScriptResource.FullName, commonCartridgeScriptResource.FullName, true);
            }
        }

        private void AddNewFileElementToResource(XmlElement resource, string hrefAttributeValue, string namespaceUri)
        {
            XmlDocument domDocument = resource.OwnerDocument;
            XmlElement fileElement = domDocument.CreateElement("file", namespaceUri);
            this.xmlUtils.AddNewAttribute(fileElement, "href", hrefAttributeValue);
            resource.AppendChild(fileElement);
            fileElement.RemoveAttribute("xmlns");
        }

        private ScormVersion GetScormVersion(XmlElement manifest)
        {
            return this.scormUtils.GetScormVersion(manifest);
        }

        private string RemoveUrlParameterString(string url)
        {
            return this.ioUtils.RemoveUrlParameterString(url);
        }

        private XmlElement GetResources(XmlElement manifest)
        {
            return this.xmlUtils.GetNamedChildElement(manifest, "resources");
        }

        private XmlElement[] GetResourceElements(XmlElement resources)
        {
            return this.xmlUtils.GetNamedChildElements(resources, "resource");
        }

        private XmlElement[] GetFileElements(XmlElement resource)
        {
            return this.xmlUtils.GetNamedChildElements(resource, "file");
        }

        #endregion
    }
}
